package mweb

import (
	"context"
	"fmt"
	"gitee.com/dennis-mxx/gcode-common/mcontext"
	"gitee.com/dennis-mxx/gcode-common/mcore"
	"gitee.com/dennis-mxx/gcode-common/menv"
	"gitee.com/dennis-mxx/gcode-common/mexception"
	"gitee.com/dennis-mxx/gcode-common/mlogger"
	"github.com/gin-gonic/gin"
	"net/http"
	"os"
	"os/signal"
	"path"
	"strconv"
	"strings"
	"syscall"
	"time"
)

var Engine *GinEngine
var GIN_MTX_KEY = "mxx_gin_mtx_key"

type XXHandlerFunc func(rtx *RequestContext) (any, error)
type SessionCtxFunc func(ctx *gin.Context) SessionCtx
type ErrorHandler func(rtx *RequestContext, err error)
type ResultFunc func(rtx *RequestContext, result any, err error)
type LoaderHandler interface {
	Loader(engine *GinEngine)
}

type GinEngine struct {
	*gin.Engine
	BasePath   string
	SessionId  string
	Port       int64
	Stx        SessionCtxFunc
	ResFunc    ResultFunc
	ErrHandler ErrorHandler
	logger     *mlogger.TraceLogger
}

func InitWeb(logger *mlogger.TraceLogger, ginOption *menv.Web) {

	if ginOption.Mode != "" {
		gin.SetMode(ginOption.Mode)
	}
	InitSession(ginOption)

	web := gin.New()

	Engine = &GinEngine{Engine: web}
	Engine.SessionId = ginOption.SessionId

	if strings.HasSuffix(ginOption.BasePath, "/") {
		Engine.BasePath = ginOption.BasePath[0 : len(ginOption.BasePath)-1]
	} else {
		Engine.BasePath = ginOption.BasePath
	}
	Engine.Port = ginOption.Port
	Engine.Stx = func(ctx *gin.Context) SessionCtx {
		return &SessionCtxDefault{
			sessionId: Engine.SessionId,
			ctx:       ctx,
		}
	}

	Engine.ResFunc = func(rtx *RequestContext, result any, err error) {
		if err != nil {
			rtx.AbortWithStatusJSON(200, mcore.Failure(-1, "exception", (err.(error)).Error()))
		} else if result != nil {
			rtx.AbortWithStatusJSON(200, result)
		}
	}
	Engine.ErrHandler = func(rtx *RequestContext, err error) {
		rtx.Logger().Error("interface exception [", rtx.Request.RequestURI, "] ", err)
		rtx.AbortWithStatusJSON(200, mcore.Failure(-1, "exception", err.Error()))
	}
	ip, _ := mcore.LocalIP()

	logger.Info(fmt.Sprintf("Initialize [Gin Port : %v , Session: %v , Url : http://%s:%v%s ]", ginOption.Port, ginOption.Session, ip, ginOption.Port, ginOption.BasePath))

}

func (domain *GinEngine) WithMiddleware(env menv.EnvironmentModel, listens ...gin.RecoveryFunc) *GinEngine {
	domain.WithLogger(env)
	domain.Use(gin.CustomRecovery(func(ctx *gin.Context, err any) {
		if vr, ok := err.(mexception.Exception); ok {
			ctx.AbortWithStatusJSON(200, mcore.Failure(vr.Code, "exception", vr.Error()))
		} else {
			ctx.AbortWithStatusJSON(200, mcore.Failure(-1, "exception", (err.(error)).Error()))
		}
		for _, listen := range listens {
			listen(ctx, err)
		}
	}))
	return domain
}

func (domain *GinEngine) WithLogger(env menv.EnvironmentModel) *GinEngine {
	if menv.Environment.Value == "pro" {
		domain.Use(gin.LoggerWithWriter(mlogger.GetWriter(env.ServerName+".log", env.Logger)))
	} else {
		domain.Use(gin.Logger())
	}
	return domain
}

func (domain *GinEngine) XXHandler(httpMethod, relativePath string, handler XXHandlerFunc) {
	domain.Handle(httpMethod, domain.getPath(relativePath), domain.GetXXHandler(handler))
}

func (domain *GinEngine) XXDel(relativePath string, handler XXHandlerFunc) {
	domain.Handle("DELETE", domain.getPath(relativePath), domain.GetXXHandler(handler))
}

func (domain *GinEngine) XXGet(relativePath string, handler XXHandlerFunc) {
	domain.Handle("GET", domain.getPath(relativePath), domain.GetXXHandler(handler))
}

func (domain *GinEngine) XXLoader(loaders ...LoaderHandler) {
	for _, loader := range loaders {
		loader.Loader(domain)
	}
}

func (domain *GinEngine) XXPost(relativePath string, handler XXHandlerFunc) {
	domain.Handle("POST", domain.getPath(relativePath), domain.GetXXHandler(handler))
}

func (domain *GinEngine) XXPut(relativePath string, handler XXHandlerFunc) {
	domain.Handle("PUT", domain.getPath(relativePath), domain.GetXXHandler(handler))
}

func (domain *GinEngine) GetXXHandler(handler XXHandlerFunc) gin.HandlerFunc {
	return func(ctx *gin.Context) {
		session := domain.Stx(ctx)
		rtx := &RequestContext{Ctx: ctx, Request: ctx.Request, Stx: session, Mtx: WithContext(ctx)}
		defer func() {
			if err := recover(); err != nil {
				domain.ErrHandler(rtx, err.(error))
			}
		}()
		result, err := handler(rtx)
		domain.ResFunc(rtx, result, err)
	}
}

func (domain *GinEngine) XXRun() {
	builder := strings.Builder{}
	builder.WriteString(":")
	builder.WriteString(strconv.FormatInt(domain.Port, 10))
	srv := &http.Server{
		Addr:    builder.String(),
		Handler: domain.Engine,
	}
	go func() {
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			domain.logger.Info("listen:%s\n", err)
		}
	}()

	// wait for interrupt signal to gracefully shutdown the server with
	// a timeout of 10 seconds.
	quit := make(chan os.Signal, 1)
	// kill (no param) default send syscall.SIGTERM
	// kill -2 is syscall.SIGINT
	// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit
	domain.logger.Info("Shutdown Server ...")

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	if err := srv.Shutdown(ctx); err != nil {
		domain.logger.Error("Server Shutdown:", err)
	}
	domain.logger.Info("Server exiting")
}

func (domain *GinEngine) With404JSON() *GinEngine {
	domain.Use(func(ctx *gin.Context) {
		if ctx.Writer.Status() == 404 {
			ctx.JSON(404, mcore.Failure(404, "404", "PATH NOT FOUND"))
		}
		ctx.Next()
	})
	return domain
}

func (domain *GinEngine) WithCors(allowHeaders ...string) *GinEngine {
	allowString := strings.Join(allowHeaders, ", ")
	domain.Use(func(ctx *gin.Context) {
		ctx.Header("Access-Control-Allow-Origin", ctx.GetHeader("Origin")) // 注意这一行，不能配置为通配符“*”号
		ctx.Header("Access-Control-Allow-Credentials", "true")             // 注意这一行，必须设定为 true
		ctx.Header("Access-Control-Allow-Headers", allowString)            // 我们自定义的header字段都需要在这里声明
		ctx.Header("Access-Control-Allow-Methods", "POST,GET,OPTIONS,DELETE,PUT")
		ctx.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type,cache-control")
		if ctx.Request.Method == "OPTIONS" {
			ctx.AbortWithStatus(http.StatusNoContent)
			return
		}
		ctx.Next()
	})
	return domain
}
func (domain *GinEngine) getPath(relativePath string) string {
	return path.Join(domain.BasePath, relativePath)
}

func WithContext(gin *gin.Context) *mcontext.MContext {
	if mtx := gin.Value(GIN_MTX_KEY); mtx != nil {
		return mtx.(*mcontext.MContext)
	}
	mtx := mcontext.WithContext(context.Background())
	if traceId := gin.GetHeader(mlogger.TRACE_ID); traceId != "" {
		mtx.SetTraceId(traceId)
	}
	return mtx

}
